#define _GNU_SOURCE

#include <sys/ipc.h>
#include <sys/msg.h>
#include <errno.h>
#include "syscore.h"
#include "msgproc.x"
#include "common.h"
#include "modules.h"
#include "../logServer/clogger.h"

#define MSGLogDebug(fmt, args...) CLogger(MODULE_MESSAGE, __FILE__, __LINE__, LOG_DEBUG, fmt, ##args);
#define MSGLogInfo(fmt, args...) CLogger(MODULE_MESSAGE, __FILE__, __LINE__, LOG_INFO, fmt, ##args);
#define MSGLogNotice(fmt, args...) CLogger(MODULE_MESSAGE, __FILE__, __LINE__, LOG_NOTICE, fmt, ##args);
#define MSGLogWarning(fmt, args...) CLogger(MODULE_MESSAGE, __FILE__, __LINE__, LOG_WARNING, fmt, ##args);
#define MSGLogError(fmt, args...) CLogger(MODULE_MESSAGE, __FILE__, __LINE__, LOG_ERR, fmt, ##args);
#define MSGLogCrit(fmt, args...) CLogger(MODULE_MESSAGE, __FILE__, __LINE__, LOG_CRIT, fmt, ##args);
#define MSGLogAlert(fmt, args...) CLogger(MODULE_MESSAGE, __FILE__, __LINE__, LOG_ALERT, fmt, ##args);
#define MSGLogEmerg(fmt, args...) CLogger(MODULE_MESSAGE, __FILE__, __LINE__, LOG_EMERG, fmt, ##args);

int queue_tx = 0;
int queue_rx = 0;
void* MSGPROC_external(void* h);
void* MSGPROC_internal(void* h);

void MSGPROC_extCmdProc(MSGHEADER *pExtMsg);

ERROR_CODE MSGPROC_decodeDest(void *h, U8* paraValue, U32 paraLength );
ERROR_CODE MSGPROC_decodeMsg(void *h, U8* paraValue, U32 paraLength );

typedef struct tagMSGFORWARDPARA {
    U32 mask;
    U32 msgLen;
    U8 *msg;
    long dst;    
} MSGFORWARDPARA;

typedef struct tagMSGPROCSTATS {
    U32 numExternalRcvd;
    U32 numExternalDropped;
    U32 numExternalForwardToInternal;
    U32 numExternalForwardToInternalFailed;
    U32 numExternalForwardToExternal;
    U32 numExternalForwardToExternalFailed;
    U32 numInternalRcvd;
    U32 numInternalDropped;
    U32 numInternalForwardToExternal;
    U32 numInternalForwardToExternalFailed;
    U32 numInternalForwardToInternal;
    U32 numInternalForwardToInternalFailed;
}MSGPROCSTATS;

MSGPROCSTATS msgprocStats = {0};

PARADECODERMAP forwardParaDecTab[] = {
    {PARA_TYPE_MSG_FORWARD_DEST,MSGPROC_decodeDest},
    {PARA_TYPE_MSG_FORWARD_MSG,MSGPROC_decodeMsg}
};
#ifdef MODULE_TEST
static bool testMsg = false;
#endif

#define MESSAGE_HAERTBEAT_INTERVAL 5000
void* pMsgHeartBeatTimer = NULL;
void Message_heartbeat_timer(void* h)
{ 
    U32 rc = PARA_VALUE_SYSMONITOR_MODULE_OK;
    CM_SendMsg_1Para(MODULE_SYSMONITOR, MODULE_MESSAGE, MSG_TYPE_COMMAND, CMD_EVENT, CMD_CODE_SYSMONITOR_HEARTBEAT, 0,
        PARA_TYPE_SYSMONITOR_MODULE_STATE,sizeof(U32), (U8*)&rc);   
     
    MOD_timerRestart(pMsgHeartBeatTimer, MESSAGE_HAERTBEAT_INTERVAL);
}

RESULT MSGPROC_sendExternalMsg_forExternal(void* pExMsgHeader, U16 msgLength)
{
    MSGHEADER *pmsg_hdr = (MSGHEADER*)pExMsgHeader;
    PCMDHEADER pCmdHeader = (PCMDHEADER)(((char *)pmsg_hdr) + sizeof(MSGHEADER));
   // debug_dump_buffer(msgLength, pExMsgHeader);
    MSGLogInfo("Forward msgqueue message: src=0x%04X dst=0x%04X(%d) len=%d cmdType=%x cmdCode=%x",
        pmsg_hdr->srcID, pmsg_hdr->dstID, pmsg_hdr->mType,
        msgLength, pCmdHeader->cmdType, pCmdHeader->cmdCode);
    int rc = msgsnd(queue_tx, (void*)pExMsgHeader, msgLength, 0);
    if(rc != 0)
    {    
        MSGLogError("faile to send msgqueue message, %s",strerror(errno));
        return RC_FAIL;
    }
    else
    {
        return RC_OK;
    }
}

void MSGPROC_sendExternalMsg_forInternal(MSGHEADER *msg_hdr)
{
    PCMDHEADER pCmdHeader = (PCMDHEADER)(((char*)msg_hdr) + sizeof(MSGHEADER));

    msg_hdr->mType = get_mtype(msg_hdr->dstID);
    MSGLogInfo("Send msgqueue message: mtpye=%d src=0x%04X dst=0x%04X type=%d len=%x cmdType=%x cmdCode=%x",
        msg_hdr->mType, msg_hdr->srcID, msg_hdr->dstID, msg_hdr->msgType, msg_hdr->msgLength,
        pCmdHeader->cmdType, pCmdHeader->cmdCode);

    int rc = msgsnd(queue_tx, (void*)msg_hdr, msg_hdr->msgLength, 0);
    if( rc != 0 )
    {    
        MSGLogError("faild to send msgqueue msessage, %s",strerror(errno));
        msgprocStats.numInternalForwardToExternalFailed++;
    }
    else
    {
        msgprocStats.numInternalForwardToExternal++;
    }
}

RESULT MSGPROC_init(void)
{
    key_t key;
    pthread_t pthrd;
    MODREG modReg = {0};
    key = ftok("/tmp", MSGQ_FTOK_ID);
	if (key == (key_t)-1) 
    {
		dbgprintf(DEBUG_ERROR,stderr, "ftok(%d) failed, %s", DEV_MSG_ID, strerror(errno));
		return RC_FAIL;
   	}
    queue_tx = msgget(key, IPC_CREAT);

	key = ftok("/tmp", MSGQ_FTOK_ID ^ 0xff);
	if (key == (key_t)-1) 
    {
		dbgprintf(DEBUG_ERROR,stderr, "ftok(%d) failed, %s", DEV_MSG_ID ^ 0xff, strerror(errno));
       
	    if(queue_tx != 0)
        {   
		    msgctl(queue_tx, IPC_RMID, NULL);
            queue_tx = 0;   
        }
		return RC_FAIL;
   	}
	queue_rx = msgget(key, IPC_CREAT);

    modReg.modID = MODULE_MESSAGE;
    modReg.needTick = TRUE;
    modReg.tickPeriod = 5000; // 5s
    modReg.needModTimer = TRUE;
    
    MOD_register(&modReg);
        
    if(pthread_create(&pthrd, NULL, MSGPROC_external, NULL) != RC_OK)
    {
        return RC_FAIL;
    } 
    
    if(pthread_create(&pthrd, NULL, MSGPROC_internal, NULL) != RC_OK)
    {
        return RC_FAIL;
    }
    
    return RC_OK;
}


void MSGPROC_nack_extmsg(MSGHEADER *pRcvMsgHeader, U32 errCode)
{
    U64 ackBuff[MSG_BUFF_SIZE/8] = {0};
    PMSGHEADER pAckMsgHeader = (PMSGHEADER)ackBuff;
    PCMDHEADER pAckHeader = (PCMDHEADER)(((U8*)ackBuff) + sizeof(MSGHEADER));
    PCMDHEADER pRcvHeader = (PCMDHEADER)(((U8*)pRcvMsgHeader) + sizeof(MSGHEADER));

    pAckMsgHeader->mType = get_mtype(pRcvMsgHeader->srcID);
    pAckMsgHeader->srcID = MODULE_MESSAGE;
    pAckMsgHeader->dstID = pRcvMsgHeader->srcID;
    pAckMsgHeader->msgType = MSG_TYPE_COMMAND;

    pAckHeader->cmdIndex = pRcvHeader->cmdIndex;
    pAckHeader->cmdCode = pRcvHeader->cmdCode;
    pAckHeader->cmdLength = CM_paraPackError((PPARAM)(pAckHeader+1), errCode);
    pAckHeader->cmdType = CMD_GENERAL_RESPONSE;

    pAckMsgHeader->msgLength = sizeof(PMSGHEADER) + sizeof(PCMDHEADER) + pAckHeader->cmdLength;
    
    MSGLogError("NACK to external message %x", errCode);
    MSGPROC_sendExternalMsg_forExternal((void*)pAckMsgHeader, pAckMsgHeader->msgLength);
}

void* MSGPROC_external(void* h)
{   
    ssize_t len;
    U64 msgbuff[MSG_BUFF_SIZE/8];
    PMSGHEADER pMsgHeader = (PMSGHEADER)(&msgbuff[0]);
    PCMDHEADER pCmdHeader = (PCMDHEADER)(((U8*)pMsgHeader) + sizeof(MSGHEADER));
    #ifdef MULTICMD 
    U32 total_len,length;
    PPARAM pParam = (PPARAM) (pCmdHeader+1);
    PMSGHEADER  pCmd;
    #endif    
    int err;
    RESULT rc;

    pthread_setname_np(pthread_self(), "msgproc_ex");
    if(queue_rx != 0)
    {
        while(TRUE)
        {
            len = msgrcv(queue_rx, (void*)pMsgHeader, DEVM_CMD_LENGTH, 0, MSG_NOERROR);
            err = errno;
            if(len <= 0)
            {
                if(err != EINTR)
                {
                    MSGLogWarning("Error %s",strerror(err));
                }
                continue;                          
            }

            MSGLogInfo("Recv msgqueue message: mtype=%d src=0x%04X dst=0x%04X msgtype=%d len=%d",
                pMsgHeader->mType, pMsgHeader->srcID, pMsgHeader->dstID, pMsgHeader->msgType, pMsgHeader->msgLength);
           // debug_dump_buffer(len, pMsgHeader);
            msgprocStats.numExternalRcvd++;

            if( pMsgHeader->mType != MSGQ_TYPE_MSG )
            {
                /* external message, forward it */
                MSGPROC_sendExternalMsg_forExternal((void*)pMsgHeader, pMsgHeader->msgLength);
            }
            else
            { 
#ifdef MULTICMD
                total_len = 0;
                pCmd = pCmdHeader;
                length = 0;
                while(total_len < len)
                {
                    pCmd = (PCMDHEADER)((U8*)pCmd + length);
                    length = roundup8(sizeof(CMDHEADER)+ pCmd->cmdLength);                
                    total_len += length;    
                }

                if( len < total_len - length + sizeof(MSGHEADER) + sizeof(CMDHEADER) + pCmd->cmdLength )           
#else
                if( len < sizeof(MSGHEADER) + sizeof(CMDHEADER) + pCmdHeader->cmdLength - sizeof(long) )
#endif
                {
                    MSGLogError("illegal msgqueue message: len %d < %d + %d + %d",
                            len, sizeof(MSGHEADER), sizeof(CMDHEADER), pCmdHeader->cmdLength);
                    msgprocStats.numExternalDropped++;
                    continue;
                }

                if( pMsgHeader->dstID != MODULE_MESSAGE )
                {
                    rc = MSG_send(pMsgHeader->dstID, pMsgHeader, pCmdHeader);
                    if( rc == RC_OK )
                    {
                        msgprocStats.numExternalForwardToInternal++;
                    }
                    else
                    {
                        msgprocStats.numExternalForwardToInternalFailed++;
                        switch(rc)
                        {
                            case RC_MSG_DST_INVALID:
                                MSGPROC_nack_extmsg(pMsgHeader, PARA_VALUE_ERROR_DST_MODULE_INACTIVE);
                                break;
                            case RC_MSG_QUEUE_FULL:
                                MSGPROC_nack_extmsg(pMsgHeader, PARA_VALUE_ERROR_DST_MODULE_BUSY);
                                break;
                            case RC_MSG_NO_BUFF:
                                MSGPROC_nack_extmsg(pMsgHeader, PARA_VALUE_ERROR_NO_MEMORY);
                                break;
                            default:
                                break;
                        }
                    }
                }
                else
                {
                    MSGPROC_extCmdProc(pMsgHeader);
                }
            }
        }
    }
    else
    {
        MSGLogError("Error in receive exmsg: no message queue",NULL);
    }

    return NULL;
}

void* MSGPROC_internal(void *h)
{
    U64 msgbuff[MSG_BUFF_SIZE/8];
    PMSGHEADER pMsgHeader =(PMSGHEADER) msgbuff;
    PCMDHEADER pCmdHeader = (PCMDHEADER)( ((U8*)msgbuff) + sizeof(MSGHEADER) );
 
    pthread_setname_np(pthread_self(), "msgproc_in");

    pMsgHeartBeatTimer = MOD_timerStart(MODULE_MESSAGE, MESSAGE_HAERTBEAT_INTERVAL, Message_heartbeat_timer, NULL);

    while(TRUE)
    {
        MSG_read(MODULE_MESSAGE, pMsgHeader, pCmdHeader);
#ifdef MODULE_TEST
        if ( testMsg )
        {
            MSGLogError("----- test module message ----- %04x", CMD_CODE_SYSMONITOR_TEST_MODULE);            
            while(1)
            {

            }
        }

#endif
        msgprocStats.numInternalRcvd++;
        if( pMsgHeader->msgType == MSG_TYPE_COMMAND )
        {                        
            if( get_mtype(pMsgHeader->dstID) != MSGQ_TYPE_MSG )
            {
                MSGPROC_sendExternalMsg_forInternal(pMsgHeader);
            }
            else
            {
                msgprocStats.numInternalDropped++;
                
            }
        }
        else if( pMsgHeader->msgType == MSG_TYPE_TICK)
        {
            MOD_timerProc(MODULE_MESSAGE);
        }
        else
        {
            msgprocStats.numInternalDropped++;
            // do nothing
            MSGLogError("Internal message is dropped: src=0x%04X, dst=0x%04X, msgType=%d",
                pMsgHeader->srcID, pMsgHeader->dstID, pMsgHeader->msgType);
        }
    }

    return NULL;
}


ERROR_CODE MSGPROC_decodeDest(void *h, U8* paraValue, U32 paraLength )
{
    MSGFORWARDPARA* buff = (MSGFORWARDPARA*)h;

    if(paraLength == sizeof(U32))
    {
        U32 dst = *((U32*)paraValue);
        if( dst > 0 )
        {
            buff->mask |= (0x1);
            buff->dst = dst;
            return PARA_VALUE_ERROR_NO_ERROR;
        }
        else
        {
            return PARA_VALUE_ERROR_INVALID_PARAVALUE;
        }
    }
    else
    {
        return PARA_VALUE_ERROR_INVALID_PARALENGTH;
    }
}

ERROR_CODE MSGPROC_decodeMsg(void *h, U8* paraValue, U32 paraLength )
{
    MSGFORWARDPARA* buff = (MSGFORWARDPARA*)h;

    if(paraLength > 0 )
    {
        
        buff->mask |= (0x1 << 1);
        buff->msgLen= paraLength;
        buff->msg = paraValue;
        
        return PARA_VALUE_ERROR_NO_ERROR;
    }
    else
    {
        return PARA_VALUE_ERROR_INVALID_PARALENGTH;
    }

}

void MSGPROC_extCmdProc(MSGHEADER *pExtMsg)
{                        
    U64 respBuff[MSG_BUFF_SIZE/8];
    U32 needAck = TRUE;
    CMDHEADER *pExtCmd = (CMDHEADER *)(((U8 *)pExtMsg) + sizeof(MSGHEADER));
    MSGHEADER *pAckMsgHeader = (MSGHEADER *)respBuff;
    PCMDHEADER pAckCmdHeader = (PCMDHEADER)(((U8*)pAckMsgHeader) + sizeof(MSGHEADER));

    pAckMsgHeader->srcID = MODULE_MESSAGE;
    pAckMsgHeader->dstID = pExtMsg->srcID;
    pAckMsgHeader->mType = get_mtype(pAckMsgHeader->dstID);
    pAckMsgHeader->msgType = MSG_TYPE_COMMAND;

    pAckCmdHeader->cmdCode = pExtCmd->cmdCode;
    pAckCmdHeader->cmdIndex = pExtCmd->cmdIndex;
    pAckCmdHeader->cmdLength = 0;
    switch(pExtCmd->cmdType)
    {
        case CMD_SET:            
        {
            pAckCmdHeader->cmdType = CMD_SET_RESPONSE;
#ifdef MODULE_TEST
            if (  pExtCmd->cmdCode == CMD_CODE_SYSMONITOR_TEST_MODULE )
            {
                testMsg = true;
                needAck = FALSE;
            }
#endif

            break;
        }
 
        default:
            pAckCmdHeader->cmdType = CMD_GENERAL_RESPONSE;//pCmdHeader->cmdType;  
            pAckCmdHeader->cmdLength = CM_paraPackError((PPARAM)(pAckCmdHeader+1), PARA_VALUE_ERROR_UNKNOWN_CMDTYPE);
            MSGLogError("Unknown cmd type 0x%04x from %ld",pExtCmd->cmdType,pExtMsg->srcID);
            break;
    }   

    if( needAck )
    {
        pAckMsgHeader->msgLength = sizeof(MSGHEADER) + sizeof(CMDHEADER) + pAckCmdHeader->cmdLength;
        MSGPROC_sendExternalMsg_forExternal((void*)pAckMsgHeader, pAckMsgHeader->msgLength);
    }
}

void MSGPROC_printStats(void)
{
    dbgprintf_s(DEBUG_INFO, stdout, "***********   MODID[0x%x] Message processor statistics    ***************\n", MODULE_MESSAGE);
    dbgprintf_s(DEBUG_INFO, stdout, "ExternalRcvd = %u, ExternalDropped = %u, ExternalFwdToInternal = %u, ExternalFwdToInternalFailed = %u\n",msgprocStats.numExternalRcvd,msgprocStats.numExternalDropped,msgprocStats.numExternalForwardToInternal,msgprocStats.numExternalForwardToInternalFailed);
    dbgprintf_s(DEBUG_INFO, stdout, "ExternalFwdToExternal = %u, ExternalFwdToExternalFailed = %u\n",msgprocStats.numExternalForwardToExternal,msgprocStats.numExternalForwardToExternalFailed);
    dbgprintf_s(DEBUG_INFO, stdout, "InternalRcvd = %u, InternalDropped = %u, InternalFwdToExternal = %u, InternalFwdToExternalFailed = %u\n",msgprocStats.numInternalRcvd,msgprocStats.numInternalDropped,msgprocStats.numInternalForwardToExternal,msgprocStats.numInternalForwardToExternalFailed);
    dbgprintf_s(DEBUG_INFO, stdout, "InternalFwdInternal = %u, InternalFwdInternalFailed = %u\n",msgprocStats.numInternalForwardToInternal,msgprocStats.numInternalForwardToInternalFailed);        
}


